package org.ccay.excel.importer.service.impl.internal;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import ognl.NullHandler;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.OgnlRuntime;

import org.ccay.core.log.ILogger;
import org.ccay.core.log.CcayLoggerFactory;
import com.opensymphony.xwork2.util.CompoundRoot;

/**
 * NullHandler的一个实现，用来实例化一个嵌套的对象。
 * 	
 * @author chaowangbang
 * @since Mar 23, 2012
 */
public class InstantiatingNullHandler implements NullHandler {
	
	/** 日志记录器 **/
	private ILogger logger = CcayLoggerFactory.getLogger(InstantiatingNullHandler.class);

	/* (non-Javadoc)
	 * @see ognl.NullHandler#nullMethodResult(java.util.Map, java.lang.Object, java.lang.String, java.lang.Object[])
	 */
	@SuppressWarnings("rawtypes")
	public Object nullMethodResult(Map context, Object target,
			String methodName, Object[] args) {
		return null;
	}

	/* (non-Javadoc)
	 * @see ognl.NullHandler#nullPropertyValue(java.util.Map, java.lang.Object, java.lang.Object)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object nullPropertyValue(Map context, Object target, Object property) {
		if ((target == null) || (property == null)) {
            return null;
        }
		
		try {
			// 属性名称
			String propName = property.toString();
			// 获取属性类型
			Object realTarget = getRealTarget(propName, context, target);
			
			Class<?> clazz = getPropertyType(realTarget, propName);
            if (clazz == null) {
                // can't do much here!
                return null;
            }
            
            // 创建一个实例对象
            Object param = createObject(clazz);
            
            Ognl.setValue(propName, context, realTarget, param);
            
            return param;
		} catch (Exception ex) {
			logger.error("Could not create and/or set value back on to object.", ex);
		}
		
		return null;
	}
	
	/**
	 * 获取属性的类型。
	 * 
	 * @param realTarget
	 * @param propName
	 * @return
	 * @throws OgnlException 
	 * @throws IntrospectionException 
	 */
	private Class<?> getPropertyType(Object realTarget, String propName) throws IntrospectionException, OgnlException {
		if(realTarget == null) {
			return null;
		}
		
		PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor(realTarget.getClass(), propName);
		if (pd == null) {
			return null;
		}

		return pd.getPropertyType();
       
	}
	
	/** 获取真正的属性对象	
	 * @author chaowangbang
	 * @since Mar 23, 2012
	 * @param property
	 * @param context
	 * @param root
	 * @return
	 * @throws OgnlException
	 */
	private Object getRealTarget(String property, Map<String, Object> context, Object root) throws OgnlException {
        if (!(root instanceof CompoundRoot)) {
        	return root;
        }
        
        // find real target
        CompoundRoot cr = (CompoundRoot) root;
        try {
        	for (Object target : cr) {
        		if(isRealTarget((OgnlContext)context, target, property)) {
        			return target;
        		}
        	}
        } catch (IntrospectionException ex) {
        	throw new OgnlException("Cannot figure out real target class", ex);
        }

        return null;
       
    }
	
	private boolean isRealTarget(OgnlContext context, Object target, String property) throws IntrospectionException, OgnlException {
		if (OgnlRuntime.hasSetProperty(context, target, property)) {
			return true;
		} else if(OgnlRuntime.hasGetProperty(context, target, property)) {
			return true;
		} else if(OgnlRuntime.getIndexedPropertyType(context, target.getClass(), property) != OgnlRuntime.INDEXED_PROPERTY_NONE) {
			return true;
		}
		
		return false;
	}
	
	/** 实例化一个指定类型的对象。	
	 * @author chaowangbang
	 * @since Mar 23, 2012
	 * @param clazz
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("rawtypes")
	private Object createObject(Class<?> clazz) throws InstantiationException, IllegalAccessException {
        if (Collection.class.isAssignableFrom(clazz)) {
            return new ArrayList();
        } else if (clazz == Map.class) {
            return new HashMap();
        }

        return clazz.newInstance();
    }

}
